﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.Serialization;
using Elderos.Controls;
using Microsoft.Samples.CustomControls;
using TreeBuilder.Settings;

namespace TreeBuilder
{
	/// <summary>
	/// Interaction logic for LineLink.xaml
	/// </summary>
	public partial class LineLink : UserControl, IBinder, INameScope , IPrintable, IXmlSerializable
	{

		public LineLink()
		{
			InitializeComponent();
			Initialize(LineSettings.Default.Stroke,
				LineSettings.Default.Thickness,
				LineSettings.Default.DashStyle);
		}

		private void Initialize(Color color, double thickness, DoubleCollection dashArray)
		{
			Line.Stroke = new SolidColorBrush(color);
			Line.StrokeThickness = thickness;
			Line.StrokeDashArray = dashArray;

			NameScope.SetNameScope(Menu, this);
		}

		private double _realThickness;
		public double RealThickness
		{
			get { return _realThickness; }
			private set
			{
				_realThickness = value;
			}
		}

		private bool _thicknessChanged;

		protected override void OnMouseEnter(MouseEventArgs e)
		{
			base.OnMouseEnter(e);
			if (Line.StrokeDashArray != null && Line.StrokeDashArray.Count > 0) return;
			RealThickness = Line.StrokeThickness;
			_thicknessChanged = true;
			Line.StrokeThickness = Line.StrokeThickness + 2;
		}

		protected override void OnMouseLeave(MouseEventArgs e)
		{
			base.OnMouseLeave(e);
			if (_thicknessChanged)
				NormalizeThickness();
		}

		private void NormalizeThickness()
		{
			Line.StrokeThickness = RealThickness;
			_thicknessChanged = false;
		}

		public INode From { get; set; }
		public INode To { get; set; }

		public void RefreshPoints()
		{
			if (From == null || To == null)
			{
				this.Visibility = Visibility.Hidden;
				return;
			}

			double x1,  y1;
			double x2, y2;

			if (To.Center.Y > From.BottomCenter.Y)
			{
				y1 = From.BottomCenter.Y;
				y2 = To.Center.Y;
				x1 = From.BottomCenter.X;
				x2 = To.Center.X;
			}
			else if (From.Center.Y > To.BottomCenter.Y)
			{
				y1 = To.BottomCenter.Y;
				y2 = From.Center.Y;
				x1 = To.BottomCenter.X;
				x2 = From.Center.X;
			}//надо рисовать с боков
			else
			{
				if (To.LeftCenter.X > From.RightCenter.X)
				{
					y1 = From.RightCenter.Y;
					y2 = To.LeftCenter.Y;
					x1 = From.RightCenter.X;
					x2 = To.LeftCenter.X;
				}
				else if (From.LeftCenter.X > To.RightCenter.X)
				{
					y1 = From.LeftCenter.Y;
					y2 = To.RightCenter.Y;
					x1 = From.LeftCenter.X;
					x2 = To.RightCenter.X;
				}
				else //default
				{
					this.Visibility = Visibility.Collapsed;
					return;
				}
			}

			this.Visibility = Visibility.Visible;

			Line.X1 = x1;
			Line.Y1 = y1;
			Line.X2 = x2;
			Line.Y2 = y2;

            InvalidateAdorners();
		}

		public event Action<object, DeleteBinderEventArgs> Delete;

		protected virtual void OnDelete(object sender, DeleteBinderEventArgs e)
		{
			Action<object, DeleteBinderEventArgs> handler = Delete;
			if (handler != null) handler(sender, e);
		}

		#region INameScope Members

		readonly Dictionary<string, object> _items = new Dictionary<string, object>();

		object INameScope.FindName(string name)
		{
            return _items[name];
		}

		void INameScope.RegisterName(string name, object scopedElement)
		{
			_items.Add(name, scopedElement);
		}

		void INameScope.UnregisterName(string name)
		{
			_items.Remove(name);
		}

		#endregion

		private void DeleteMenuItem_Click(object sender, RoutedEventArgs e)
		{
			OnDelete(sender, new DeleteBinderEventArgs(this));
		}

		#region Implementation of IPrintable

		public bool IsBeingPrinted
		{
			get { return (bool)GetValue(IsBeingPrintedProperty); }
			set
			{
				SetValue(IsBeingPrintedProperty, value);
				if (value && _thicknessChanged)
					NormalizeThickness();
			}
		}

		// Using a DependencyProperty as the backing store for IsBeingPrinted.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty IsBeingPrintedProperty =
			DependencyProperty.Register("IsBeingPrinted", typeof(bool), typeof(LineLink), new UIPropertyMetadata(false));

		#endregion

		#region Implementation of IXmlSerializable

		/// <summary>
		/// This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class.
		/// </summary>
		/// <returns>
		/// An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> method.
		/// </returns>
		public XmlSchema GetSchema()
		{
			return null;
		}

		/// <summary>
		/// Generates an object from its XML representation.
		/// </summary>
		/// <param name="reader">The <see cref="T:System.Xml.XmlReader"/> stream from which the object is deserialized. </param>
		public void ReadXml(XmlReader reader)
		{
			XDocument xdoc = XDocument.Load(reader);
			var root = xdoc.Root;

			Color color = (Color)XamlReader.Parse(root.Element("Color").Value);
			double thickness = double.Parse(root.Element("Thickness").Value.Replace(",", "."), CultureInfo.InvariantCulture);
			DoubleCollection dashStyle = DoubleCollection.Parse(root.Element("DashStyle").Value.Replace(",", "."));

			Initialize(color, thickness, dashStyle);
		}

		/// <summary>
		/// Converts an object into its XML representation.
		/// </summary>
		/// <param name="writer">The <see cref="T:System.Xml.XmlWriter"/> stream to which the object is serialized. </param>
		public void WriteXml(XmlWriter writer)
		{
			writer.WriteElementString("Color", XamlWriter.Save(((SolidColorBrush)Line.Stroke).Color));
			writer.WriteElementString("Thickness", Line.StrokeThickness.ToString(CultureInfo.InvariantCulture));
			writer.WriteElementString("DashStyle", Line.StrokeDashArray == null ? "" : Line.StrokeDashArray.ToString());
			writer.WriteElementString("From", From.Guid.ToString());
			writer.WriteElementString("To", To.Guid.ToString());
		}

		#endregion

		private bool ValidateDoubleCollection(string arg)
		{
			return ValidatableTextBox.ValidateDoubleCollection(arg);
		}

		private bool ValidateDouble(string arg)
		{
			return ValidatableTextBox.ValidateDouble(arg);
		}

		private bool ValidateThickness(string arg)
		{
			if (!ValidatableTextBox.ValidateDouble(arg)) return false;
			double value;
			if (!double.TryParse(arg.Replace(",", "."), NumberStyles.Number, CultureInfo.InvariantCulture, out value))
				return false;

			return value <= ApplicationSettings.Default.MaxBinderThickness
				   && value >= ApplicationSettings.Default.MinBinderThickness;
		}

		private bool ParseDoubleCollection(string arg)
		{
			return ValidatableTextBox.ParseDoubleCollection(arg);
		}

		public Dictionary<string, object> MakeSnapshot()
		{
			var snapshot = new Dictionary<string, object>();
			snapshot.Add("Thickness", Line.StrokeThickness);
			snapshot.Add("Dashes", Line.StrokeDashArray);
			snapshot.Add("Brush", Line.Stroke);

			return snapshot;
		}

		public void RestoreSnapshop(Dictionary<string, object> snapshot)
		{
			if (snapshot.ContainsKey("Thickness") && Line.StrokeThickness != (double)snapshot["Thickness"])
				Line.StrokeThickness = (double)snapshot["Thickness"];
			if (snapshot.ContainsKey("Dashes") && Line.StrokeDashArray != (DoubleCollection)snapshot["Dashes"])
				Line.StrokeDashArray = (DoubleCollection)snapshot["Dashes"];
			if (snapshot.ContainsKey("Brush") && Line.Stroke != (Brush)snapshot["Brush"])
				Line.Stroke = (Brush)snapshot["Brush"];
		}

		public bool DetectChanges(Dictionary<string, object> snapshot)
		{
			if (snapshot.ContainsKey("Thickness") && Line.StrokeThickness != (double)snapshot["Thickness"])
				return true;
			if (snapshot.ContainsKey("Dashes") && Line.StrokeDashArray != (DoubleCollection)snapshot["Dashes"])
				return true;
			if (snapshot.ContainsKey("Brush") && Line.Stroke != (Brush)snapshot["Brush"])
				return true;
			return false;
		}

		public event Action<object, MenuChangesEventArgs> MenuOpened;

		protected virtual void OnMenuOpened(object sender, MenuChangesEventArgs e)
		{
			Action<object, MenuChangesEventArgs> handler = MenuOpened;
			if (handler != null) handler(sender, e);
		}

		public event Action<object, MenuChangesEventArgs> MenuClosed;

		protected virtual void OnMenuClosed(object sender, MenuChangesEventArgs e)
		{
			Action<object, MenuChangesEventArgs> handler = MenuClosed;
			if (handler != null) handler(sender, e);
		}

		private void Menu_OnOpened(object sender, RoutedEventArgs e)
		{
			OnMenuOpened(this, new MenuChangesEventArgs(this));
		}

		private void Menu_OnClosed(object sender, RoutedEventArgs e)
		{
			OnMenuClosed(this, new MenuChangesEventArgs(this));
		}

	    private void ColorPicker_OnSelectedColorChanged(object sender, RoutedPropertyChangedEventArgs<Color> e)
	    {
	        Line.Stroke = new SolidColorBrush(e.NewValue);
	    }

        public bool Intersects(Rect selectionRect)
        {
            var geometry = new RectangleGeometry(selectionRect);
            var startPoint = new Point(Line.X1, Line.Y1);
            var lineSegment = new LineSegment(new Point(Line.X2, Line.Y2), true);
            Geometry polyline = new PathGeometry(new[] { new PathFigure(startPoint, new[]{lineSegment}, true) });

            var intersection = geometry.FillContainsWithDetail(polyline);

            return intersection != IntersectionDetail.Empty;
        }

	    public bool IsSelected
        {
            get { return (bool)GetValue(IsSelectedProperty); }
            set
            {
                SetValue(IsSelectedProperty, value);
                InvalidateAdorners();
            }
        }

        private void InvalidateAdorners()
        {
            var adornerLayer = AdornerLayer.GetAdornerLayer(Line);
            if (adornerLayer == null) return;

            var adorners = adornerLayer.GetAdorners(Line);
            if (adorners == null) return;

            foreach (var adorner in adorners)
            {
                adorner.InvalidateVisual();
            }
        }

	    public static readonly DependencyProperty IsSelectedProperty =
            DependencyProperty.Register("IsSelected", typeof(bool), typeof(LineLink), new PropertyMetadata(false));

	    public event Action<object, SelectionEventArgs> Selected;

	    protected virtual void OnSelected(object arg1, SelectionEventArgs arg2)
	    {
	        Action<object, SelectionEventArgs> handler = Selected;
	        if (handler != null) handler(arg1, arg2);
	    }

	    public void InitializeSelectionAdorners()
	    {
            var adornerLayer = AdornerLayer.GetAdornerLayer(Line);
            var adorner = new LineAdorner(Line);

            var binding = new Binding("IsSelected");
            binding.Source = this;

            adorner.SetBinding(EllipseAdorner.IsActiveProperty, binding);
            adornerLayer.Add(adorner);
	    }

	    private void Line_OnLoaded(object sender, RoutedEventArgs e)
	    {
            InitializeSelectionAdorners();
	    }

	    private void Line_OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
	    {
	        OnSelected(this, new SelectionEventArgs(this));
	    }
	}
}
